This second lecture on Naive Bayes deepens the practical side: we implement NB's smoothing step-by-step
as an in-class activity, run a Python end-to-end demo with scikit-learn, compare the three major NB
variants (Multinomial, Bernoulli, Gaussian), and study how text gets vectorized (Bag-of-Words, TF-IDF) for
NB-based spam detection. We close with a bias-variance-style decomposition of NB's error and a
three-dataset benchmark comparing NB against kNN and Decision Trees.
Learning Objectives
Apply Laplace smoothing independently to every feature (in-class activity)
Choose the correct NB variant: MultinomialNB, BernoulliNB, or GaussianNB
Build and evaluate a scikit-learn NB spam pipeline: CountVectorizer → MultinomialNB
Explain TF-IDF reweighting and its effect on spam classification
Benchmark NB vs. kNN vs. Trees on speed, accuracy, and robustness across three datasets
2. Theory
2.1 In-Class Activity — Laplace-Smooth All Play-Golf Features
Recall the Play-Golf dataset with 5 "No" training rows. We already smoothed Outlook for "No" (|V|Outlook = 3).
Complete the remaining three features for the "No" class with α = 1:
GaussianNB fits a per-class per-feature normal distribution. For numerical stability, scikit-learn adds a tiny epsilon \(\epsilon=10^{-9}\) to every variance so no variance is ever exactly zero.
Always scale/standardize numeric features if you want all features to contribute comparable Gaussian log-likelihood magnitudes.
2.3 From Raw Text to NB — Vectorization
NB cannot operate on strings. We first convert each document into a fixed-length numeric vector.
2.4 TF-IDF Formalized
Term Frequency × Inverse Document Frequency weights down words that appear everywhere (the, a, of)
and boosts words that are rare and hence discriminative.
\[
\text{tf-idf}(t, d, D) = \underbrace{f_{t,d}}_{\text{TF}} \;\times\; \underbrace{\log\frac{|D|}{|\{d' \in D \mid t \in d'\}|}}_{\text{IDF}}
\]
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import roc_auc_score
pipe = Pipeline([
('vect', CountVectorizer(stop_words='english',
min_df=5,
ngram_range=(1,2))),
('tfidf', TfidfTransformer()),
('clf', MultinomialNB(alpha=0.5))
])
pipe.fit(X_train, y_train)
y_proba = pipe.predict_proba(X_val)[:, 1]
print(f"Validation AUC: {roc_auc_score(y_val, y_proba):.4f}")
2.6 Benchmark — NB vs. kNN vs. Decision Trees
Dataset
Metric
kNN (k=5)
NB (Gauss/Multi)
Tree (depth 5)
Iris (num, 4f)
Accuracy
0.967
0.960 (Gaussian)
0.953
SMS Spam (text)
AUC
0.82
0.985 (MNB)
0.90
Adult (mixed, 14f)
AUC
0.83
0.86 (MNB)
0.88
Training time (relative)
10×
1×
3×
🔍 Benchmark Observations (click to expand)
NB wins big on text (SMS spam) — the conditional-independence assumption is surprisingly effective when features are words.
All three methods are competitive on clean, low-dimensional numeric data (Iris).
Decision Trees pull ahead on mixed tabular (Adult) because they model non-linear feature interactions and splits — something NB cannot do.
NB is consistently the fastest trainer by an order of magnitude — strong as a baseline first model.
2.7 When NB Works (and When It Fails)
👍 NB Shines When
👎 NB Struggles When
Small training sets (low variance)
Strongly correlated features exist
Text / high-dimensional sparse inputs
You need calibrated probabilities (use Platt scaling)
Streaming / incremental updates required
Feature interactions drive the prediction
A low-compute baseline is needed
Num features is tiny & signal is all-interaction
3. Interactive Examples
Example 1: GaussianNB on Iris
Two-class (Setosa vs. Virginica) slice of Iris. Fitted per-class Gaussian parameters (Petal-Length cm):
Setosa: \(\mu=1.46, \sigma^2=0.03\); Virginica: \(\mu=5.55, \sigma^2=0.30\).
(a) A new flower has Petal-Length = 3.0 cm. Which class does GaussianNB favor?
Compute log-likelihood ratio using \(\log \mathcal N = -\frac{(x-\mu)^2}{2\sigma^2} - \log\sigma\).
Ratio favors Setosa over Virginica by ~2.5 nats → predict Setosa.
(3 cm is 5σ away from Virginica's mean, but only ~8σ from Setosa —
Virginica's larger variance softens the blow but not enough!)
(b) Why is \(\sigma^2_{Virginica}=0.30\) so much larger than \(\sigma^2_{Setosa}=0.03\)?
Virginica petal lengths are genuinely more spread out in nature than Setosa's (which are tightly clustered).
GaussianNB learns different per-class per-feature variances and uses them correctly.
Example 2: TF-IDF Intuition
A 10,000-document email corpus. "the" appears in 9,900 docs; "viagra" appears in 100 docs.
Compute IDF("the") and IDF("viagra").
IDF("the") = log(10000 / 9900) ≈ 0.01 (near-zero weight)
IDF("viagra") = log(10000 / 100) ≈ 4.605 (~460× more discriminative weight!)
TF-IDF therefore essentially drops stop-words from the classification automatically, even without a stop-word list.
Example 3: Benchmark Choice
A startup ships a spam filter on a Raspberry Pi (very low CPU) and must retrain daily on 100K new labeled emails.
Accuracy is "good enough" at any score ≥ 0.95 AUC; training-time budget: 30 seconds.
Choose the best model from {kNN, GaussianNB, MultinomialNB, DecisionTree} and justify in 1 sentence.
MultinomialNB with TF-IDF: text input → multinomial is correct; MNB trains 10× faster than kNN and hits ≥ 0.98 AUC on SMS spam in the benchmark — comfortably above 0.95 within the time budget.
4. Numerical Solutions
Problem 1: GaussianNB on 2-Class 2-Feature Toy Data
Class A (n=3): samples \((1,2), (2,3), (3,4)\) · Class B (n=3): \((6,7), (7,8), (8,9)\).
Class A wins by about 0.35 nats (despite B having a flatter wider Gaussian, x=1.5 is much closer to 0 than to 4).
Problem 2 — BernoulliNB Play Golf
We convert the 4-category Outlook feature into 3 Bernoulli dummy features (IsSunny, IsOvercast, IsRain). P(IsOvercast∣No, α=1, |V|=2 per feature) = ?
Hint: We're now working per dummy, so vocabulary size is 2 (true/false). The No class has 5 training rows.
count(IsOvercast=T, No) = 0, count(No)=5. Pₛ = (0+1)/(5+2) = 1/7 ≈ 0.143.
(This is the same "rare event with smoothing" situation — BernoulliNB dummies just make each binary feature explicit.)
Problem 3 — NB Incremental Throughput
Batch 1: 1,000,000 docs (700 K spam, 300 K ham). Count("sale"∣spam) = 200 K; Count("sale"∣ham) = 6 K.
Batch 2: 100,000 new docs arrive. Count("sale"∣spam) = 18 K; Count("sale"∣ham) = 500.
(i) What are the merged counts? (ii) What are the merged P("sale"∣spam) and P("sale"∣ham) without smoothing?
Merged spam docs: 700K+? — need to solve Batch 2 spam/ham split!
Assume Batch 2 class distribution is 50K spam / 50K ham for the problem:
Answer all 5 MCQs. Click on an option to get instant feedback.
Your score: 0 / 5
7. Key Takeaways
Laplace smoothing applies to every feature. Use the vocabulary size \(|V_i|\) of that specific feature in the denominator. Don't reuse one feature's |V| for another.
3 NB variants for 3 data types: GaussianNB = continuous features; MultinomialNB = integer/count (bag-of-words text); BernoulliNB = binary presence/absence.
Text → fixed vectors via CountVectorizer or TF-IDF. IDF crushes near-universal words (the, of, and) automatically, letting rare discriminative words dominate.
NB is speed king for text — 10× faster than kNN, 3× faster than shallow trees, with best-in-class AUC on text. Use NB as your first baseline before trying expensive models.
Incremental merging is exact. Add frequency tables element-by-element for every mini-batch. Recomputing from scratch is wasteful and unnecessary!
Bias-variance intuition: NB has high bias (strong independence assumptions) but extremely low variance — it wins in small-data / high-d regimes where low-variance methods dominate.
8. Common Pitfalls
Using GaussianNB on bag-of-words counts. BOW integers are not normally-distributed — use MultinomialNB instead. The mismatch usually costs 5–10 % AUC.
Applying raw CountVectorizer without min_df / stop-words / pruning. 10⁵+ vocabulary blows up memory; hapax legomena (words seen once) hurt generalization.
Sharing the same α across all NB variants blindly. α=1 (Laplace) is a default; for MultinomialNB on text, tune α ∈ {0.1, 0.5, 1, 2} on a validation set to squeeze out 1–2 % AUC.
Interpreting NB's predicted probabilities as well-calibrated. The independence assumption distorts magnitudes. Use Platt scaling / isotonic regression via CalibratedClassifierCV if calibrated probabilities matter.
TF-IDF on already-normalized likelihoods. Apply TF-IDF to the raw count matrix, then feed the reweighted matrix to MultinomialNB — don't try to apply it after NB training (too late).
Benchmarking a single train-test split only. NB is stable but always use 5-fold CV with fixed random seed when comparing models — a lucky split can easily lie by 3 %.